#pragma once

#include <iostream>
#include <queue>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>

using namespace std;

const uint32_t gDefaultCap = 5;

template <class T>
class BlockQueue
{
public:
    BlockQueue(uint32_t cap = gDefaultCap) :cap_(cap)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&conCond_, nullptr);
        pthread_cond_init(&proCond_, nullptr);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&conCond_);
        pthread_cond_destroy(&proCond_);
    }

public:
    // 生产接口
    void push(const T &in) // const&: 纯输入
    {
        // 加锁
        // 判断->是否适合生产->bq是否为满->程序员视角的条件->1.满(不生产) 2.不满(生产)
        // if(满) 不生产，休眠
        // else if(不满) 生产，唤醒消费者
        // 解锁

        lockQueue();
        // 必须使用while，因为下面wait被唤醒后不一定条件是满足的
        while (isFull())
        {
            // before: 等待的时候，会自动释放mutex_
            proBlockWait(); // 阻塞等待，等待被唤醒  被唤醒 != 条件被满足
            // after: 醒来的时候，是在临界区里醒来的
        }
        // 条件满足，可以生产
        pushCore(in); // 生产完成
        // wakeupCon() // 写在这唤醒消费者也可以
        unlockQueue();
        wakeupCon(); // 唤醒消费者
    }

    // 消费接口
    T pop()
    {
        // 加锁
        // 判断->是否适合消费->bq是否为空->程序员视角的条件->1.空(不消费) 2.有(消费)
        // if(空) 不消费，休眠
        // else if(有) 消费，唤醒生产者
        // 解锁

        lockQueue();
        if(isEmpty())
        {
            conBlockWait(); // 阻塞等待，等待被唤醒
        }
        // 条件满足，可以消费
        T tmp = popCore();
        unlockQueue();
        wakeupPro(); // 唤醒生产者

        return tmp;
    }

private:
    void lockQueue()
    {
        pthread_mutex_lock(&mutex_);
    }

    void unlockQueue()
    {
        pthread_mutex_unlock(&mutex_);
    }

    bool isEmpty()
    {
        return bq_.empty();
    }

    bool isFull()
    {
        return bq_.size() == cap_;
    }

    void proBlockWait() // 生产者一定是在临界区中的
    {
        // 1. 在阻塞线程的时候，会自动释放mutex_锁
         pthread_cond_wait(&proCond_, &mutex_);
         // 2. 当阻塞结束，返回的时候，pthread_cond_wait，会自动帮你重新获得mutex_，然后才返回
    }

    void conBlockWait() // 阻塞等待，等待被唤醒
    {
        // 1. 在阻塞线程的时候，会自动释放mutex_锁
        pthread_cond_wait(&conCond_, &mutex_);
         // 2. 当阻塞结束，返回的时候，pthread_cond_wait，会自动帮你重新获得mutex_，然后才返回
    }

    void wakeupPro() // 唤醒生产者
    {
        pthread_cond_signal(&proCond_);
    }

    void wakeupCon() // 唤醒消费者
    {
        pthread_cond_signal(&conCond_);
    }

    void pushCore(const T &in)
    {
        bq_.push(in); // 生产完成
    }

    T popCore()
    {
        T tmp = bq_.front();
        bq_.pop();
        return tmp;
    }
    
private:
    uint32_t cap_; // 容量
    queue<T> bq_; // blockqueue
    pthread_mutex_t mutex_; // 保护阻塞队列的互斥锁
    pthread_cond_t conCond_; // 让消费者等待的条件变量
    pthread_cond_t proCond_; // 让生产者等待的条件变量
};